/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under 
 * the Apache License, Version 2.0  (the "License"); you may not use this file
 * except in compliance with the License.  
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */


/*! \file mme_app_config.c
   \brief
   \author Lionel GAUTHIER
   \version 1.0
   \company Eurecom
   \email: lionel.gauthier@eurecom.fr
*/
#define MME_APP
#define MME_APP_CONFIG_C

#include <string.h>
#include <libconfig.h>
#include "mme_app_config.h"
#include "dynamic_memory_check.h"
#include "log.h"

int
mme_app_config_init (
  char *lib_config_file_name_pP,
  mme_app_config_t * config_pP)
{
  config_t                                cfg;
  config_setting_t                       *setting_sgw = NULL;
  char                                   *sgw_interface_name_for_S1u_S12_S4_up = NULL;
  char                                   *sgw_ipv4_address_for_S1u_S12_S4_up = NULL;
  char                                   *sgw_interface_name_for_S5_S8_up = NULL;
  char                                   *sgw_ipv4_address_for_S5_S8_up = NULL;
  char                                   *sgw_interface_name_for_S11 = NULL;
  char                                   *sgw_ipv4_address_for_S11 = NULL;
  config_setting_t                       *setting_pgw = NULL;
  config_setting_t                       *subsetting = NULL;
  config_setting_t                       *sub2setting = NULL;
  char                                   *pgw_interface_name_for_S5_S8 = NULL;
  char                                   *pgw_ipv4_address_for_S5_S8 = NULL;
  char                                   *pgw_interface_name_for_SGI = NULL;
  char                                   *pgw_ipv4_address_for_SGI = NULL;
  char                                   *delimiters = NULL;
  char                                   *saveptr1 = NULL;
  char                                   *astring = NULL;
  char                                   *atoken = NULL;
  char                                   *atoken2 = NULL;
  char                                   *address = NULL;
  char                                   *cidr = NULL;
  char                                   *mask = NULL;
  int                                     num = 0;
  int                                     i = 0;
  int                                     jh,
                                          jn;
  unsigned char
                                          buf_in6_addr[sizeof (struct in6_addr)];
  struct in6_addr                         addr6_start;
  struct in6_addr                         addr6_mask;
  int                                     prefix_mask;
  uint64_t                                counter64;
  unsigned char
                                          buf_in_addr[sizeof (struct in_addr)];
  struct in_addr                          addr_start;
  struct in_addr                          addr_end;

  memset ((char *)config_pP, 0, sizeof (mme_app_config_t));
  config_init (&cfg);

  if (lib_config_file_name_pP ) {
    /*
     * Read the file. If there is an error, report it and exit.
     */
    if (!config_read_file (&cfg, lib_config_file_name_pP)) {
      MME_APP_ERROR ("%s:%d - %s\n", lib_config_file_name_pP, config_error_line (&cfg), config_error_text (&cfg));
      config_destroy (&cfg);
      AssertFatal (1 == 0, "Failed to parse eNB configuration file %s!\n", lib_config_file_name_pP);
    }
  } else {
    SPGW_APP_ERROR ("No SP-GW configuration file provided!\n");
    config_destroy (&cfg);
    AssertFatal (0, "No SP-GW configuration file provided!\n");
  }

  setting_sgw = config_lookup (&cfg, SGW_CONFIG_STRING_SGW_CONFIG);

  if (setting_sgw ) {
    subsetting = config_setting_get_member (setting_sgw, SGW_CONFIG_STRING_NETWORK_INTERFACES_CONFIG);

    if (subsetting ) {
      if ((config_setting_lookup_string (subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S1U_S12_S4_UP, (const char **)&sgw_interface_name_for_S1u_S12_S4_up)
           && config_setting_lookup_string (subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S1U_S12_S4_UP, (const char **)
                                            &sgw_ipv4_address_for_S1u_S12_S4_up)
           && config_setting_lookup_string (subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S5_S8_UP, (const char **)
                                            &sgw_interface_name_for_S5_S8_up)
           && config_setting_lookup_string (subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S5_S8_UP, (const char **)
                                            &sgw_ipv4_address_for_S5_S8_up)
           && config_setting_lookup_string (subsetting, SGW_CONFIG_STRING_SGW_INTERFACE_NAME_FOR_S11, (const char **)
                                            &sgw_interface_name_for_S11)
           && config_setting_lookup_string (subsetting, SGW_CONFIG_STRING_SGW_IPV4_ADDRESS_FOR_S11, (const char **)
                                            &sgw_ipv4_address_for_S11)
          )
        ) {
        config_pP->sgw_config.ipv4.sgw_interface_name_for_S1u_S12_S4_up = STRDUP_CHECK (sgw_interface_name_for_S1u_S12_S4_up);
        cidr = STRDUP_CHECK (sgw_ipv4_address_for_S1u_S12_S4_up);
        address = strtok (cidr, "/");
        mask = strtok (NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO (address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S1u_S12_S4_up, "BAD IP ADDRESS FORMAT FOR S1u_S12_S4 !\n")
          config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S1u_S12_S4_up = atoi (mask);
        FREE_CHECK (cidr);
        config_pP->sgw_config.ipv4.sgw_interface_name_for_S5_S8_up = STRDUP_CHECK (sgw_interface_name_for_S5_S8_up);
        cidr = STRDUP_CHECK (sgw_ipv4_address_for_S5_S8_up);
        address = strtok (cidr, "/");
        mask = strtok (NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO (address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S5_S8_up, "BAD IP ADDRESS FORMAT FOR S5_S8 !\n")
          config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S5_S8_up = atoi (mask);
        FREE_CHECK (cidr);
        config_pP->sgw_config.ipv4.sgw_interface_name_for_S11 = STRDUP_CHECK (sgw_interface_name_for_S11);
        cidr = STRDUP_CHECK (sgw_ipv4_address_for_S11);
        address = strtok (cidr, "/");
        mask = strtok (NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO (address, config_pP->sgw_config.ipv4.sgw_ipv4_address_for_S11, "BAD IP ADDRESS FORMAT FOR S11 !\n")
          config_pP->sgw_config.ipv4.sgw_ip_netmask_for_S11 = atoi (mask);
        FREE_CHECK (cidr);
      }
    }
  }

  setting_pgw = config_lookup (&cfg, PGW_CONFIG_STRING_PGW_CONFIG);

  if (setting_pgw ) {
    subsetting = config_setting_get_member (setting_pgw, SGW_CONFIG_STRING_NETWORK_INTERFACES_CONFIG);

    if (subsetting ) {
      if ((config_setting_lookup_string (subsetting, PGW_CONFIG_STRING_PGW_INTERFACE_NAME_FOR_S5_S8, (const char **)&pgw_interface_name_for_S5_S8)
           && config_setting_lookup_string (subsetting, PGW_CONFIG_STRING_PGW_IPV4_ADDRESS_FOR_S5_S8, (const char **)
                                            &pgw_ipv4_address_for_S5_S8)
           && config_setting_lookup_string (subsetting, PGW_CONFIG_STRING_PGW_INTERFACE_NAME_FOR_SGI, (const char **)
                                            &pgw_interface_name_for_SGI)
           && config_setting_lookup_string (subsetting, PGW_CONFIG_STRING_PGW_IPV4_ADDR_FOR_SGI, (const char **)
                                            &pgw_ipv4_address_for_SGI)
          )
        ) {
        config_pP->pgw_config.ipv4.pgw_interface_name_for_S5_S8 = STRDUP_CHECK (pgw_interface_name_for_S5_S8);
        cidr = STRDUP_CHECK (pgw_ipv4_address_for_S5_S8);
        address = strtok (cidr, "/");
        mask = strtok (NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO (address, config_pP->pgw_config.ipv4.pgw_ipv4_address_for_S5_S8, "BAD IP ADDRESS FORMAT FOR S5_S8 !\n")
          config_pP->pgw_config.ipv4.pgw_ip_netmask_for_S5_S8 = atoi (mask);
        FREE_CHECK (cidr);
        config_pP->pgw_config.ipv4.pgw_interface_name_for_SGI = STRDUP_CHECK (pgw_interface_name_for_SGI);
        cidr = STRDUP_CHECK (pgw_ipv4_address_for_SGI);
        address = strtok (cidr, "/");
        mask = strtok (NULL, "/");
        IPV4_STR_ADDR_TO_INT_NWBO (address, config_pP->pgw_config.ipv4.pgw_ipv4_address_for_SGI, "BAD IP ADDRESS FORMAT FOR SGI !\n")
          config_pP->pgw_config.ipv4.pgw_ip_netmask_for_SGI = atoi (mask);
        FREE_CHECK (cidr);
      }
    }

    subsetting = config_setting_get_member (setting_pgw, PGW_CONFIG_STRING_IP_ADDRESS_POOL);

    if (subsetting ) {
      sub2setting = config_setting_get_member (subsetting, PGW_CONFIG_STRING_IPV4_ADDRESS_LIST);

      if (sub2setting ) {
        num = config_setting_length (sub2setting);

        for (i = 0; i < num; i++) {
          astring = config_setting_get_string_elem (sub2setting, i);

          if (astring ) {
            trim (astring, strlen (astring) + 1);

            if (inet_pton (AF_INET, astring, buf_in_addr) < 1) {
              // failure, test if there is a range specified in the string
              atoken = strtok (astring, PGW_CONFIG_STRING_IP_ADDRESS_RANGE_DELIMITERS);

              if (inet_pton (AF_INET, astring, buf_in_addr) == 1) {
                memcpy (&addr_start, buf_in_addr, sizeof (struct in_addr));
                // valid address
                atoken2 = strtok (NULL, PGW_CONFIG_STRING_IP_ADDRESS_RANGE_DELIMITERS);

                if (inet_pton (AF_INET, atoken2, buf_in_addr) == 1) {
                  memcpy (&addr_end, buf_in_addr, sizeof (struct in_addr));

                  // valid address
                  for (jh = ntohl (addr_start.s_addr); jh <= ntohl (addr_end.s_addr); jh++) {
                    DevAssert (PGW_MAX_ALLOCATED_PDN_ADDRESSES > config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses);
                    jn = htonl (jh);

                    if (IN_CLASSA (addr_start.s_addr)) {
                      if ((jh & 0xFF) && (jh & 0xFF) != 0xFF) {
                        config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = jn;
                      }
                    } else if (IN_CLASSB (addr_start.s_addr)) {
                      if ((jh & 0xFF) && (jh & 0xFF) != 0xFF) {
                        config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = jn;
                      }
                    } else if (IN_CLASSC (addr_start.s_addr)) {
                      if ((jh & 0xFF) && (jh & 0xFF) != 0xFF) {
                        config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = jn;
                      }
                    } else {
                      OAI_FPRINTF_ERR("ERROR ON ADDRESS CLASS %d.%d.%d.%d\n", NIPADDR (jn));
                    }
                  }
                }
              }
            } else {
              DevAssert (PGW_MAX_ALLOCATED_PDN_ADDRESSES > config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses);
              memcpy (&addr_start, buf_in_addr, sizeof (struct in_addr));
              config_pP->pgw_config.pool_pdn_addresses.ipv4_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv4_addresses++].s_addr = addr_start.s_addr;
            }
          }
        }
      }

      sub2setting = config_setting_get_member (subsetting, PGW_CONFIG_STRING_IPV6_ADDRESS_LIST);

      if (sub2setting ) {
        num = config_setting_length (sub2setting);

        for (i = 0; i < num; i++) {
          astring = config_setting_get_string_elem (sub2setting, i);

          if (astring ) {
            trim (astring, strlen (astring) + 1);

            if (inet_pton (AF_INET6, astring, buf_in6_addr) < 1) {
              // failure, test if there is a range specified in the string
              atoken = strtok (astring, PGW_CONFIG_STRING_IPV6_PREFIX_DELIMITER);

              if (inet_pton (AF_INET6, astring, buf_in6_addr) == 1) {
                atoken2 = strtok (NULL, PGW_CONFIG_STRING_IPV6_PREFIX_DELIMITER);
                prefix_mask = atoi (atoken2);
                // arbitrary values
                DevAssert ((prefix_mask < 128) && (prefix_mask >= 64));
                memcpy (&addr6_start, buf_in6_addr, sizeof (struct in6_addr));
                memcpy (&addr6_mask, buf_in6_addr, sizeof (struct in6_addr));
                sgw_ipv6_mask_in6_addr (&addr6_mask, prefix_mask);

                if (memcmp (&addr6_start, &addr6_mask, sizeof (struct in6_addr)) != 0) {
                  AssertFatal (0, "BAD IPV6 ADDR CONFIG/MASK PAIRING %s/%d\n", astring, prefix_mask);
                }

                counter64 = 0xFFFFFFFFFFFFFFFF >> prefix_mask;  // address Prefix_mask/0..0 not valid

                do {
                  addr6_start.s6_addr32[3] = addr6_start.s6_addr32[3] + htonl (1);

                  if (addr6_start.s6_addr32[3] == 0) {
                    addr6_start.s6_addr32[2] = addr6_start.s6_addr32[2] + htonl (1);

                    if (addr6_start.s6_addr32[2] == 0) {
                      // should not happen since mask is no less than 64
                      addr6_start.s6_addr32[1] = addr6_start.s6_addr32[1] + htonl (1);

                      if (addr6_start.s6_addr32[1] == 0) {
                        addr6_start.s6_addr32[0] = addr6_start.s6_addr32[0] + htonl (1);
                      }
                    }
                  }

                  memcpy (&config_pP->pgw_config.pool_pdn_addresses.ipv6_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv6_addresses++], &addr6_start, sizeof (struct in6_addr));
                  counter64 = counter64 - 1;
                } while (counter64 > 0);
              }
            } else {
              DevAssert (PGW_MAX_ALLOCATED_PDN_ADDRESSES > config_pP->pgw_config.pool_pdn_addresses.num_ipv6_addresses);
              memcpy (&config_pP->pgw_config.pool_pdn_addresses.ipv6_addresses[config_pP->pgw_config.pool_pdn_addresses.num_ipv6_addresses++], buf_in6_addr, sizeof (struct in6_addr));
            }
          }
        }
      }
    }
  }

  return 0;
}
